#include "config.h"

#include "dushd.h"

typedef struct {
	int	fd;
	dush_fileopt_t	opt;
} fileop_info_t;

static ecm_ptrs_t	*fileop_infos;

static dush_err_t
do_file_create(const char *c_path, const char *fname, int *pfd, dush_fileopt_t opt)
{
	char		*path = NULL;
	ecm_err_t	ecm_err;
	ECMBOOL		retry = ECM_FALSE;

	if (ecm_is_directory(c_path)) {
		path = ecm_gen_path(c_path, fname, NULL);
		c_path = path;
	}
	if (ecm_access(c_path, ECM_F_OK) && ((opt & DUSH_FILEOPT_FORCE) == 0)) {
		ecm_free(path);
		return -ERR_DUSH_EXIST;
	}

	ecm_err = ecm_creat(pfd, c_path, ECM_P_RWUSR);
	ecm_free(path);

	if (ecm_err == ECM_OK)
		return DUSH_OK;
	return -ERR_DUSH_GENERAL;
}

static ECMBOOL
write_data(int fd, ecm_uint64 offset, sr_t *sr_oct)
{
	ecm_size_t	size, orgsize;

	orgsize = size = sr_get_oct_size(sr_oct);

	ecm_lseek(fd, (ecm_int64)offset, ECM_SEEK_SET);
	if (!ecm_write(fd, sr_get_oct_data(sr_oct), &size) || size != orgsize)
		return ECM_FALSE;
	return ECM_TRUE;
}

static sr_t *
fileop_create(sr_t *sr_info)
{
	const char	*fname, *path;
	int		fd;
	dush_fileopt_t	opt;
	dush_err_t	err;
	sr_t		*sr_res;

	fname = sr_geti_str(sr_info, 1); 
	path = sr_geti_str(sr_info, 2);
	opt = sr_geti_int(sr_info, 3);

	sr_res = sr_create_dir();
	err = do_file_create(path, fname, &fd, opt);
	sr_append(sr_res, sr_create_int(ST_INT, err));
	if (err == DUSH_OK) {
		fileop_info_t	*info;
		ecm_uint32	idx;

		info = ecm_malloc(sizeof(fileop_info_t));
		info->fd = fd;
		info->opt = opt;
		idx = ecm_ptrs_add(fileop_infos, info);
		sr_append(sr_res, sr_create_int(ST_INT, idx));
	}
	return sr_res;
}

static sr_t *
fileop_close(sr_t *sr_info)
{
	fileop_info_t	*info;
	ecm_uint32	idx;

	idx = sr_geti_int(sr_info, 1);
	info = ecm_ptrs_remove(fileop_infos, idx);
	if (info == NULL)
		return sr_create_int(ST_INT, -ERR_DUSH_NOTEXIST);
	ecm_close(info->fd);
	ecm_free(info);
	return sr_create_int(ST_INT, DUSH_OK);
}

static sr_t *
fileop_write(sr_t *sr_info)
{
	sr_t		*sr_oct;
	ecm_uint32	idx;
	fileop_info_t	*info;
	ecm_uint64	offset;

	idx = sr_geti_int(sr_info, 1);
	info = ecm_ptrs_get(fileop_infos, idx);
	if (info == NULL)
		return sr_create_int(ST_INT, -ERR_DUSH_NOTEXIST);
	offset = sr_geti_ll(sr_info, 2);
	sr_oct = sr_geti(sr_info, 3);

	if (!write_data(info->fd, offset, sr_oct))
		return sr_create_int(ST_INT, -ERR_DUSH_GENERAL);
	return sr_create_int(ST_INT, DUSH_OK);
}

static dush_err_t
file_remove(const char *path, dush_fileopt_t opt)
{
	ecm_err_t	ecm_err;

	if (!ecm_access(path, ECM_F_OK))
		return -ERR_DUSH_NOTEXIST;

	if (ecm_is_directory(path)) {
		ecm_err = ecm_rmdir_force(path);
		switch (ecm_err) {
		case ECM_OK:
			return DUSH_OK;
		default:
			break;
		}
	}
	else {
		ecm_err = ecm_unlink(path);
		switch (ecm_err) {
		case ECM_OK:
			return DUSH_OK;
		default:
			break;
		}
	}
	return -ERR_DUSH_GENERAL;
}

static sr_t *
fileop_remove(sr_t *sr_info)
{
	const char	*path;
	dush_fileopt_t	opt;

	path = sr_geti_str(sr_info, 1);
	opt = sr_geti_int(sr_info, 2);

	return sr_create_int(ST_INT, file_remove(path, opt));
}

sr_t *
dushd_cb_file(ecm_ebc_t ebc, ebc_addr_t *saddr, ebc_addr_t *daddr, sr_t *sr_info, ecm_ptr_t ctx)
{
	switch ((dush_fileop_t)sr_geti_int(sr_info, 0)) {
	case DUSH_FILEOP_CREATE:
		return fileop_create(sr_info);
	case DUSH_FILEOP_CLOSE:
		return fileop_close(sr_info);
	case DUSH_FILEOP_WRITE:
		return fileop_write(sr_info);
	case DUSH_FILEOP_REMOVE:
		return fileop_remove(sr_info);
	default:
		break;
	}
	return NULL;
}

void
dush_file_init(void)
{
	fileop_infos = ecm_ptrs_new(8);
}